/*
 * 人人信用管理有限公司版权所有.
 * 
 * @Description:
 * 
 * @author: liyuliang
 * 
 * @date: 20 Apr 2017 10:44:16 am
 * 
 * 注意：本内容仅限于人人信用管理有限公司内部传阅，禁止外泄以及用于其他的商业目的.
 */

package com.rrx360.common.web.matcher;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;

import org.springframework.util.AntPathMatcher;

/**
 * 由于filter的路径配置不是非常灵活，如exclude,特开发此类。 includePath和excludePath是精准匹配
 * includePattern和excludePattern是ant模式匹配，后面可以跟上一个参数,用逗号隔开，可以配置是否缓存,默认为false;exclude无缓存
 * 
 * @author liyuliang
 *
 */
public class ServletPathMatcher {

  private AntPathMatcher matcher = new AntPathMatcher();

  private String[] includePath;

  private String[] excludePath;

  private String[] includePattern;

  private String[] excludePattern;

  private Map<String, String> includePathSet;

  private Map<String, String> excludePathSet;

  private String[][] includePatternSet;

  private String[][] excludePatternSet;

  private boolean defaultValue = true;

  private boolean init = false;

  public ServletPathMatcher() {

  }

  public ServletPathMatcher(String[] includePattern, String[] excludePattern) {
    this(null, null, includePattern, excludePattern, true);
  }

  public ServletPathMatcher(String[] includePattern, String[] excludePattern,
      boolean defaultValue) {
    this(null, null, includePattern, excludePattern, defaultValue);
  }

  public ServletPathMatcher(String[] includePath, String[] excludePath, String[] includePattern,
      String[] excludePattern) {
    this(includePath, excludePath, includePattern, excludePattern, true);
  }

  public ServletPathMatcher(String[] includePath, String[] excludePath, String[] includePattern,
      String[] excludePattern, boolean defaultValue) {
    this.includePath = includePath;
    this.excludePath = excludePath;
    this.includePattern = includePattern;
    this.excludePattern = excludePattern;
    this.defaultValue = defaultValue;
    this.init();
  }

  @PostConstruct
  public void init() {
    if (init) {
      return;
    }
    init = true;
    includePathSet = new ConcurrentHashMap<>();
    if (includePath != null && includePath.length > 0) {
      for (String s : includePath) {
        includePathSet.put(s, s);
      }
    }
    excludePathSet = new ConcurrentHashMap<>();
    if (excludePath != null && excludePath.length > 0) {
      for (String s : excludePath) {
        excludePathSet.put(s, s);
      }
    }
    if (includePattern != null && includePattern.length > 0) {
      List<String[]> includePatternList = new ArrayList<>();
      for (int i = 0; i < includePattern.length; i++) {
        String[] infos = includePattern[i].split(",");
        if (isAccuratePath(infos[0])) {
          includePathSet.put(infos[0], infos[0]);
          continue;
        }
        if (infos.length == 1) {
          includePatternList.add(new String[] {infos[0], null});
        } else {
          includePatternList.add(new String[] {infos[0], infos[1]});
        }
      }
      includePatternSet = includePatternList.toArray(new String[0][]);
    }

    if (excludePattern != null && excludePattern.length > 0) {
      List<String[]> excludePatternList = new ArrayList<>();
      for (int i = 0; i < excludePattern.length; i++) {
        String[] infos = excludePattern[i].split(",");
        if (isAccuratePath(infos[0])) {
          excludePathSet.put(infos[0], infos[0]);
          continue;
        }
        if (infos.length == 1) {
          excludePatternList.add(new String[] {infos[0], null});
        } else {
          excludePatternList.add(new String[] {infos[0], infos[1]});
        }
      }
      excludePatternSet = excludePatternList.toArray(new String[0][]);
    }
  }

  private boolean isAccuratePath(String path) {
    return !path.contains("*") && !path.contains("?");
  }

  public boolean match(HttpServletRequest request) {
    String path = request.getServletPath();
    // 如果缺省值是不包含，则先从包含路径中匹配 匹配到后再看是否在不包含中
    if (defaultValue == false) {
      if (includePathSet != null && includePathSet.containsKey(path)) {
        return true;
      }
      if (excludePathSet != null && excludePathSet.containsKey(path)) {
        return false;
      }

      boolean includeCache = false;
      boolean include = false;
      if (includePatternSet != null) {
        for (String[] s : includePatternSet) {
          if (matcher.match(s[0], path)) {
            include = true;
            if ("true".equals(s[1])) {
              includeCache = true;
            }
            break;
          }
        }
      }

      if (!include) {
        return false;
      }

      if (excludePatternSet != null) {
        for (String[] s : excludePatternSet) {
          if (matcher.match(s[0], path)) {
            include = false;
            break;
          }
        }
      }

      if (include && includeCache) {
        includePathSet.put(path, path);
      }

      return include;

      // 如果缺省值是包含，则直接看不包含
    } else {
      if (excludePathSet != null && excludePathSet.containsKey(path)) {
        return false;
      }

      if (excludePatternSet != null) {
        for (String[] s : excludePatternSet) {
          if (matcher.match(s[0], path)) {
            if ("true".equals(s[1])) {
              excludePathSet.put(s[0], null);
            }
            return false;
          }
        }
      }
      return true;
    }

  }

  public AntPathMatcher getMatcher() {
    return matcher;
  }

  public void setMatcher(AntPathMatcher matcher) {
    this.matcher = matcher;
  }

  public String[] getIncludePath() {
    return includePath;
  }

  public void setIncludePath(String[] includePath) {
    this.includePath = includePath;
  }

  public String[] getExcludePath() {
    return excludePath;
  }

  public void setExcludePath(String[] excludePath) {
    this.excludePath = excludePath;
  }

  public String[] getIncludePattern() {
    return includePattern;
  }

  public void setIncludePattern(String[] includePattern) {
    this.includePattern = includePattern;
  }

  public String[] getExcludePattern() {
    return excludePattern;
  }

  public void setExcludePattern(String[] excludePattern) {
    this.excludePattern = excludePattern;
  }

  public boolean isDefaultValue() {
    return defaultValue;
  }

  public void setDefaultValue(boolean defaultValue) {
    this.defaultValue = defaultValue;
  }



}
